﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows;

namespace WpfTreeGridTest
{
    public class BaseViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected bool SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
        {
            if (EqualityComparer<T>.Default.Equals(field, value)) return false;
            field = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            return true;
        }
    }

    public class ColumnItemModel : BaseViewModel
    {
        public int LayerIndex { get; set; }
        public int ItemIndex { get; set; }
        public string ItemKey => $"{LayerIndex}-{ItemIndex}";

        private string _name;

        public string Name
        {
            get => _name;
            set => SetField(ref _name, value);
        }
    }

    public class ColumnCollectionModel : BaseViewModel
    {
        public event EventHandler OnCurrentLayerChange;

        private Visibility _isVisiable;

        public int Layer { get; set; }

        public Thickness Indent => new Thickness(Layer * 20 + (Layer == 1 ? 7 : 5), 0, 0, 0);

        public ObservableCollection<ColumnItemModel> Columns { get; set; }

        public Visibility IsVisiable
        {
            get => _isVisiable;
            set
            {
                SetField(ref _isVisiable, value);
                if (_isVisiable == Visibility.Visible)
                {
                    OnCurrentLayerChange?.Invoke(this, EventArgs.Empty);
                }
            }
        }
    }

    public class DataItemModel : BaseViewModel
    {
        private string _key;
        private string _value;

        public int LayerIndex { get; set; }
        public int ItemIndex { get; set; }
        public string ItemKey => $"{LayerIndex}-{ItemIndex}";

        public string Key
        {
            get => _value;
            set => SetField(ref _key, value);
        }

        public string Value
        {
            get => _value;
            set => SetField(ref _value, value);
        }
    }

    public class NodeModel : BaseViewModel
    {
        private bool _isExpanded;
        private bool _isSelected;

        public bool IsExpanded
        {
            get => _isExpanded;
            set => SetField(ref _isExpanded, value);
        }

        public bool IsSelected
        {
            get => _isSelected;
            set => SetField(ref _isSelected, value);
        }

        public string Name { get; set; }

        public int Level { get; set; }

        public ObservableCollection<NodeModel> Children { get; set; }

        public ObservableCollection<DataItemModel> DataMap { get; set; } =
            new ObservableCollection<DataItemModel>();

        public NodeModel(string name, int level)
        {
            Name = name;
            Level = level;
        }
    }

    public class MainViewModel : BaseViewModel
    {
        private int _currentLayer;

        private ObservableCollection<NodeModel> _nodeList;

        private ObservableCollection<ColumnCollectionModel> _columnNames =
            new ObservableCollection<ColumnCollectionModel>();

        public int CurrentLayer
        {
            get => _currentLayer;
            set => SetField(ref _currentLayer, value);
        }

        public ObservableCollection<NodeModel> NodeList
        {
            get => _nodeList;
            set => SetField(ref _nodeList, value);
        }

        public ObservableCollection<ColumnCollectionModel> ColumnNames
        {
            get => _columnNames;
            set => SetField(ref _columnNames, value);
        }

        private Dictionary<int, int> _levelDataMap = new Dictionary<int, int>()
        {
            { 1, 0 },
            { 2, 0 },
            { 3, 0 }
        };

        public MainViewModel()
        {
            GenerateColumns(10);
            NodeList = GenerateData(1, "def");
        }

        private void GenerateColumns(int count)
        {
            for (int i = 0; i < 3; i++)
            {
                var layer = i + 1;
                var cols = Enumerable.Range((int)Math.Pow(10, i), count)
                    .Select((m, x) => new ColumnItemModel()
                    {
                        LayerIndex = layer,
                        ItemIndex = x,
                        Name = m.ToString()
                    });

                var item = new ColumnCollectionModel()
                {
                    Layer = i + 1,
                    Columns = new ObservableCollection<ColumnItemModel>(cols),
                };

                item.OnCurrentLayerChange += (sender, args) =>
                {
                    if (sender is ColumnCollectionModel obj)
                    {
                        this.CurrentLayer = obj.Layer;
                    }
                };
                ColumnNames.Add(item);
            }
        }

        private ObservableCollection<NodeModel> GenerateData(int level, string name)
        {
            if (level <= 0 || level > 3) return null;
            var list = new ObservableCollection<NodeModel>();
            for (var i = 0; i < 10; i++)
            {
                var node = string.IsNullOrWhiteSpace(name) ? i == 0 ? "a" : i == 1 ? "b" : "c" : name;
                var nodeName = Enumerable.Range(0, level).Aggregate("", (m, x) => m + node);
                var item = new NodeModel(nodeName, level) { Children = GenerateData(level + 1, name) };

                for (var j = 0; j < 10; j++)
                {
                    item.DataMap.Add(new DataItemModel()
                    {
                        LayerIndex = level,
                        ItemIndex = j,
                        Key = $"{i}-{j}",
                        Value = j == 0 ? (++_levelDataMap[level]).ToString() : $"{j}"
                    });
                }

                list.Add(item);
            }

            return list;
        }
    }
}